package net.cl.support.json;

import java.io.IOException;
import java.util.Observable;
import java.util.Observer;
import java.util.Stack;

import net.cl.Constants;
import net.cl.utils.FileUtil;

public class JsonBuilder extends Observable {

	private int index;
	private String original;
	private StringBuilder cleanJson;
	private Stack<Character> quoteStack = new Stack<Character>();
	private Stack<Character> bracketStack = new Stack<Character>();
	private char c;
	private int status;// 0:key; 1:value
	private int mark;
	private Json json;
	private String key;
	private boolean isElement = true;

	protected JsonBuilder(String json) {
		if (json == null)
			throw new NullPointerException("json string");
		original = json;
		mark = index;
		cleanJson = new StringBuilder();
		addObserver(new Watcher());
	}

	protected void validate() {
		for (index = 0; index < original.length(); index++) {
			c = original.charAt(index);
			if (!quoteStack.isEmpty()) {
				switch (c) {
				case '\\':
					c = original.charAt(index + 1);
					switch (c) {
					case '\\':
						cleanJson.append('\\').append('\\');
						index++;
						break;
					case 'b':
					case 't':
					case 'n':
					case 'f':
					case 'r':
					case 'u':
					case 'x':
						cleanJson.append('\\');
						break;
					case '\'':
					case '"':
						cleanJson.append('\\').append(c);
						index++;
						break;
					default:
						cleanJson.append('\\').append('\\');
						break;
					}
					break;
				case '\'':
				case '"':
					if (quoteStack.peek() == c)
						quoteStack.pop();
					else
						handleEscape();
					break;
				case ':':
				case ',':
				case '[':
				case '{':
				case ']':
				case '}':
					cleanJson.append('\\').append(c);
					break;
				default:
					cleanJson.append(c);
					break;
				}
			} else {
				switch (c) {
				case '\b':
				case '\t':
				case '\n':
				case '\f':
				case '\r':
				case ' ':
					break;
				case '\'':
				case '"':
					quoteStack.push(c);
					break;
				case '[':
				case '{':
					bracketStack.push(c);
					cleanJson.append(c);
					break;
				case ']':
				case '}':
					if (bracketStack.isEmpty()
							|| (!bracketStack.isEmpty() && bracketStack.peek() != Constants.PAIR.get(c)))
						throw new JsonException("Invalid json string. Unmatched bracket '" + c + "'");
					else
						bracketStack.pop();
					cleanJson.append(c);
					break;
				default:
					cleanJson.append(c);
					break;
				}
			}
		}
		original = cleanJson.toString();
		if (!quoteStack.isEmpty())
			throw new JsonException("Invalid json string. Unmatched quote '" + quoteStack.pop() + "'");
		if (!bracketStack.isEmpty())
			throw new JsonException("Invalid json string. Unmatched bracket '" + bracketStack.pop() + "'");
	}

	protected Json build() {
		classify();
		for (index = 0; index < original.length(); index++) {
			c = original.charAt(index);
			switch (c) {
			case '\\':
				index++;
				break;
			case ':':
				status = 0; // is key
				setChanged();
				notifyObservers();
				mark = index + 1;
				break;
			case ',':
				status = 1; // is value
				isElement = true;
				setChanged();
				notifyObservers();
				mark = index + 1;
				break;
			case '[':
			case '{':
				status = 1; // is value
				isElement = false;
				index = matchBracket();
				setChanged();
				notifyObservers();
				mark = index + 1;
				break;
			default:
				break;
			}
		}
		return json;
	}

	private int matchBracket() {
		bracketStack.clear();
		int result = -1; // TODO 避免死循环
		for (int i = index; i < original.length(); i++) {
			c = original.charAt(i);
			result = i;
			switch (c) {
			case '\\':
				i++;
				break;
			case '[':
			case '{':
				bracketStack.push(c);
				break;
			case ']':
			case '}':
				if (!bracketStack.empty() && bracketStack.peek() == Constants.PAIR.get(c))
					bracketStack.pop();
				break;
			}
			if (bracketStack.isEmpty())
				break;
		}
		return result + 1;
	}

	private void handleEscape() {
		char c1 = original.charAt(index - 1);
		if (c1 != '\\')
			cleanJson.append('\\');
		cleanJson.append(c);
	}

	protected static String unEscape(String in) {
		if (in == null)
			return null;
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < in.length(); i++) {
			char c1 = in.charAt(i);
			if (c1 == '\\') {
				c1 = in.charAt(i + 1);
				switch (c1) {
				case '\\':
				case ':':
				case ',':
				case '[':
				case '{':
				case ']':
				case '}':
					break;
				default:
					sb.append(in.charAt(i));
					break;
				}
			} else
				sb.append(c1);
		}
		return sb.toString();
	}

	protected static String convert(String in) {
		if (in == null)
			return null;
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < in.length(); i++) {
			char c1 = in.charAt(i);
			if (c1 == '\\') {
				c1 = in.charAt(i + 1);
				switch (c1) {
				case '\\':
					sb.append('\\');
					break;
				case 'b':
					sb.append('\b');
					break;
				case 't':
					sb.append('\t');
					break;
				case 'n':
					sb.append('\n');
					break;
				case 'f':
					sb.append('\f');
					break;
				case 'r':
					sb.append('\r');
					break;
				case '\'':
					sb.append('\'');
					break;
				case '"':
					sb.append('"');
					break;
				case 'u':
					int x = Integer.parseInt(in.substring(i + 2, i + 6), 16);
					c1 = (char) x;
					i += 4;
					sb.append(c1);
					break;
				case 'x':
					int y = Integer.parseInt(in.substring(i + 2, i + 4), 16);
					c1 = (char) y;
					i += 2;
					sb.append(c1);
					break;
				default:
					sb.append(c1);
					break;
				}
				i++;
			} else
				sb.append(c1);
		}
		return sb.toString().replace("\\", "");
	}

	private void classify() {
		if (original.startsWith("{") && original.endsWith("}")) {
			json = new JsonObject();
			original = original.substring(1, original.length() - 1) + ",";
		} else if (original.startsWith("[") && original.endsWith("]")) {
			json = new JsonArray();
			original = original.substring(1, original.length() - 1) + ",";
		} else
			throw new JsonException("Should not happen.");
	}

	private class Watcher implements Observer {

		@Override
		public void update(Observable o, Object arg) {
			String s = original.substring(mark, index);
			if (status == 0)
				key = unEscape(s);
			else {
				if (json instanceof JsonObject) {
					if (!isElement)
						((JsonObject) json).putJson(key, new JsonBuilder(s).build());
					else
						((JsonObject) json).putJson(key, new JsonElement(s));
				} else if (json instanceof JsonArray) {
					if (!isElement)
						((JsonArray) json).addJson(new JsonBuilder(s).build());
					else
						((JsonArray) json).addJson(new JsonElement(s));
				} else
					throw new JsonException("Should not happen.");
			}
		}
	}

	public static void main(String args[]) {
		JsonObject o = new JsonObject();
		o.putString("k1", "value");
		JsonObject o1 = new JsonObject();
		o1.putJson("k", o);
		System.out.println(o1);
		try {
			String s = FileUtil.read("D:\\column");
			JsonObject j1 = JsonObject.fromString(s);
			System.out.println(j1.toTidyString());
			JsonObject j2 = j1.getJsonObject("bean").getJsonArray("video").getJsonObject(0);
			System.out.println(j2.getString("url"));
			// System.out.println(j1.getString("key::,]2"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
